#!/usr/bin/env python
# Copyright (c) 2024 Huawei Technologies Co., Ltd.
# openUBMC is licensed under Mulan PSL v2.
# You can use this software according to the terms and conditions of the Mulan PSL v2.
# You may obtain a copy of Mulan PSL v2 at:
#         http://license.coscl.org.cn/MulanPSL2
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
# See the Mulan PSL v2 for more details.

import os
import shutil
import json
import sys
import copy
import re
from conan.tools.files import chdir
from conan.tools.files import copy
from conanbase import ConanBase

required_conan_version = ">=1.60.0"


class AppConan(ConanBase):
    @staticmethod
    def get_relative_path_paths(json_dir, json_path):
        relative_path = os.path.relpath(json_path, json_dir)
        if os.sep == '\\':
            relative_path = relative_path.replace('\\', '/')
        return relative_path.split('/')

    @staticmethod
    def get_sorted_json_files(json_dir):
        json_files = []
        for root, _, files in os.walk(json_dir):
            for file in files:
                if file.endswith(".json") and not os.path.islink(file):
                    file_path = os.path.join(root, file)
                    json_files.append(file_path)
        # 按文件路径排序，确保处理顺序一致
        json_files.sort()
        return json_files
    
    @staticmethod
    def set_dict_value(keys, k, v, mapper):
        current_dict = mapper
        for key in keys:
            if key not in current_dict:
                current_dict[key] = {}
            current_dict = current_dict[key]
        current_dict[k] = v

    def build(self):
        if self.settings.build_type == "Release":
            with chdir(self, self.source_folder):
                self.run("rm -rf ./interface_config/cli/ipmcset/dft.json")
                self.run("rm -rf ./interface_config/cli/ipmcget/dft.json")
        super(AppConan, self).build()
        
        with chdir(self, self.source_folder):
            sys.path.append(self.source_folder)
            from mapper_check import MapperCheck
            MapperCheck.check()
            schema_check = str(self.options.schema_check)
            if schema_check == "true":
                from schema_check.schema_checker import SchemaChecker
                sc = SchemaChecker()
                sc.check()
        
    def custom_ipmc_version(self):
        bmc_name = str(self.options.bmc_name)
        if bmc_name:
            ipmc_version = os.path.join(self.source_folder, "interface_config/cli/echoes/ipmcget/_version")
            with open(ipmc_version, "r") as fp:
                data = fp.read()
            data = data.replace("openUBMC INFO", f"{bmc_name} INFO")
            new_bmc = bmc_name.ljust(len("openUBMC"))
            data = re.sub(r"\bopenUBMC\b", new_bmc, data)
            with open(ipmc_version, "w") as fp:
                fp.write(data)
    
    def read_config(self):
        config_path = os.path.join(self.source_folder, "interface_config/redfish/config.json")
        with open(config_path, 'r') as file:
            config = json.load(file)
        # 获取SoftwareName和SmsName的值，如果没有配置则使用默认值
        software_name = config.get('GlobalVariable', {}).get('SoftwareName', 'openUBMC')
        sms_name = config.get('GlobalVariable', {}).get('SmsName', 'BMA')
        return software_name, sms_name

    def replace_strings_in_files(self, software_name, sms_name):
        schemastore_path = os.path.join(self.source_folder,
                                        "interface_config/redfish/static_resource/redfish/v1/schemastore/en")
        for root, _, files in os.walk(schemastore_path):
            for filename in files:
                if filename.endswith('.json'):
                    file_path = os.path.join(root, filename)
                    with open(file_path, 'r') as file:
                        content = file.read()
                    # 批量字段替换
                    content = content.replace('{{SoftwareName}}', software_name)
                    content = content.replace('{{SmsName}}', sms_name)
                    # 写回静态文件
                    with open(file_path, 'w') as file:
                        file.write(content)
    
    def json_to_lua_table(self, data, indent=0):
        indent_str = "    " * indent
        lua = ""
        if isinstance(data, dict):
            if not data:
                return "{}"
            lua += "{\n"
            for key, value in data.items():
                lua += f'{indent_str}    ["{key}"] = {self.json_to_lua_table(value, indent + 1)},\n'
            lua += f'{indent_str}}}'
        elif isinstance(data, list):
            if not data:
                return "{}"
            lua += "{\n"
            for i, value in enumerate(data, start=1):
                lua += f'{indent_str}    [{i}] = {self.json_to_lua_table(value, indent + 1)},\n'
            lua += f'{indent_str}}}'
        elif isinstance(data, str):
            # 处理字符串中的特殊字符
            escaped_str = data.strip('"').replace('\\', '\\\\').replace('"', '\\"')
            escaped_str = escaped_str.replace('\n', '\\n').replace('\r', '\\r').replace('\t', '\\t')
            lua += f'"{escaped_str}"'
        elif isinstance(data, bool):
            lua += "true" if data else "false"
        elif isinstance(data, (int, float)):
            lua += str(data)
        elif data is None:
            # 映射配置中读取到值为None时，转换为cjson.null，避免与lua nil混淆
            lua += "cjson.null"
        else:
            raise TypeError(f"Unsupported data type: {type(data)}")
        return lua
                        
    def json_to_lua(self, intf, json_dir, lua_file_path, lua_mapper_file_path, lua_route_tree_path):
        json_files = self.get_sorted_json_files(json_dir)
        lua_content = "Input.data =\n"
        mapper_content = "Input.data =\n"
        lua = {}
        mapper = {}
        if intf == "cli":
            route_tree_content = "Input.data =\n"
            route_tree = {}
        for json_file in json_files:
            with open(json_file, 'r') as f:
                data = json.load(f)
            relative_path_list = self.get_relative_path_paths(json_dir, json_file)
            
            for resource in data.get("Resources", []):
                uri = resource.get("Uri")
                if uri is None:
                    continue
                # redfish接口需要获取uri的IgnoreEtags配置，默认为空列表
                if intf == "redfish":
                    ignore_etags = resource.get("IgnoreEtags", [])
                    self.set_dict_value(relative_path_list + [uri], "IgnoreEtags", ignore_etags, mapper)
                for key, value in resource.items():
                    if key in ["Uri", "IgnoreEtags", "Interfaces"]:
                        continue
                    self.set_dict_value([uri], key, value, lua)
                for interface in resource.get("Interfaces", []):
                    method_type = interface.get("Type")
                    if method_type is None:
                        continue
                    method_type = method_type.lower()
                    self.set_dict_value(relative_path_list + [uri], method_type, True, mapper)
                    if intf == "cli":
                        self.set_dict_value(uri.split('/')[1:] + ['#methods'], method_type, True, route_tree)
                    lock_down_allow = interface.get("LockDownAllow")
                    if lock_down_allow is not None and method_type == 'get':
                        lock_down_allow = True
                    if lock_down_allow is not None:
                        lua_str = self.json_to_lua_table(lock_down_allow, 4)
                        self.set_dict_value([uri, 'methods', method_type], 'LockDownAllow', lua_str, lua)
                    rsp_body = interface.get("RspBody")
                    # redfish接口响应体需要保序，转为字符串
                    if rsp_body is not None and intf == 'redfish':
                        self.set_dict_value([uri, 'methods', method_type], 'RspBody', json.dumps(rsp_body), lua)
                    elif rsp_body is not None:
                        self.set_dict_value([uri, 'methods', method_type], 'RspBody', rsp_body, lua)
                    req_body = interface.get("ReqBody")
                    if req_body is not None and intf == 'cli':
                        self.set_dict_value([uri, 'methods', method_type], 'ReqBody', json.dumps(req_body), lua)
                    elif req_body is not None:
                        self.set_dict_value([uri, 'methods', method_type], 'ReqBody', req_body, lua)
                    action_rsp_body = interface.get("ActionResponseBody")
                    if action_rsp_body is not None:
                        lua_str = self.json_to_lua_table(json.dumps(action_rsp_body), 4)
                        self.set_dict_value([uri, 'methods', method_type], 'ActionResponseBody', lua_str, lua)
                    processing_flow = interface.get("ProcessingFlow")
                    if processing_flow is not None:
                        for pf_item in processing_flow:
                            # 对象结构的CallIf有前后依赖关系，需要转为字符串
                            if "CallIf" in pf_item:
                                pf_item["CallIf"] = json.dumps(pf_item["CallIf"])
                    for key, value in interface.items():
                        if key in ["Type", "RspBody", "ReqBody", "ActionResponseBody", "LockDownAllow"]:
                            continue
                        self.set_dict_value([uri, 'methods', method_type], key, value, lua)
        mapper_content += f"{self.json_to_lua_table(mapper, 0)}"
        lua_content += f"{self.json_to_lua_table(lua, 0)}"
        if intf == 'cli':
            route_tree_content += f"{self.json_to_lua_table(route_tree, 0)}"
            directory = os.path.dirname(lua_route_tree_path)
            if not os.path.exists(directory):
                os.makedirs(directory)
            with open(lua_route_tree_path, 'w') as f:
                f.write(route_tree_content)
                self.output.info('{} package precompilation route tree file to {} successfully'\
                            .format(intf, lua_route_tree_path))
        directory = os.path.dirname(lua_file_path)
        if not os.path.exists(directory):
            os.makedirs(directory)
        with open(lua_file_path, 'w') as f:
            f.write(lua_content)
            self.output.info('{} package precompilation config file to {} successfully'.format(intf, lua_file_path))
        directory = os.path.dirname(lua_mapper_file_path)
        if not os.path.exists(directory):
            os.makedirs(directory)
        with open(lua_mapper_file_path, 'w') as f:
            f.write(mapper_content)
            self.output.info('{} package precompilation mapper file to {} successfully'\
                            .format(intf, lua_mapper_file_path))
        

    def package(self):
        copy(self, "permissions.ini", src=os.path.join(self.source_folder, "dist"), dst=self.package_folder)
        self.custom_ipmc_version()
        software_name, sms_name = self.read_config()
        self.replace_strings_in_files(software_name, sms_name)

        super(AppConan, self).package()
        sys.path.append(self.source_folder)
        from  custom import mapping_config_patch
        
        # 各个接口的Script和Plugins配置文件需要保持文本形式，避免探测关联属性失败以及映射器批量替换字段失败
        interface = {"redfish", "web_backend", "cli", "snmp"}
        for intf in interface:
            lua_dir = os.path.join(self.source_folder, "interface_config/" + intf)
            lua_package_dir = os.path.join(self.package_folder, "opt/bmc/apps/" + intf + "/interface_config")
            for root, dirs, files in os.walk(lua_dir):
                for file in files:
                    if file.endswith(".lua") and not os.path.islink(file):
                        file_path = os.path.join(root, file)
                        file_package_path = file_path.replace(lua_dir, lua_package_dir)
                        shutil.copy2(file_path, file_package_path)
            config_path = os.path.join(self.source_folder, 'interface_config', intf)
            patch_path = os.path.join(self.source_folder, 'oem', str(self.options.oem), intf)
            if not os.path.isdir(patch_path):
                continue
            map_patch = mapping_config_patch.MappingConfigPatch(self, patch_path, config_path)
            map_patch.run()

        # 支持装备测试正向采集
        for intf in ["board_info_collector"]:
            lua_dir = os.path.join(self.source_folder, "interface_config/" + intf)
            lua_package_dir = os.path.join(self.package_folder, "opt/bmc/board_info_collector")
            if not os.path.exists(lua_package_dir):
                os.mkdir(lua_package_dir)
            for root, dirs, files in os.walk(lua_dir):
                for file in files:
                    if file.endswith(".lua") and not os.path.islink(file):
                        file_path = os.path.join(root, file)
                        file_package_path = file_path.replace(lua_dir, lua_package_dir)
                        result =os.path.split(file_package_path)
                        if not os.path.exists(result[0]):
                            os.mkdir(result[0])
                        shutil.copy2(file_path, file_package_path)
            config_path = os.path.join(self.source_folder, 'interface_config', intf)
            patch_path = os.path.join(self.source_folder, 'oem', str(self.options.oem), intf)
            if not os.path.isdir(patch_path):
                continue
            map_patch = mapping_config_patch.MappingConfigPatch(self, patch_path, config_path)
            map_patch.run()

        # 支持各个接口映射配置预编译
        interface_mapper = {
            "redfish": ["mapping_config"],
            "web_backend": ["mapping_config"],
            "cli": ["ipmcget", "ipmcset"],
            "snmp": ["mapping_config"],
            "board_info_collector": ["config"]
        }
        for intf, sub_dirs in interface_mapper.items():
            for sub_dir in sub_dirs:
                json_dir = os.path.join(self.source_folder, "interface_config", intf, sub_dir)
                if intf == "board_info_collector":
                    lua_package_dir = os.path.join(self.package_folder, "opt/bmc/board_info_collector")
                else:
                    lua_package_dir = os.path.join(self.package_folder, "opt/bmc/apps", intf, "interface_config")
                lua_file_path = os.path.join(lua_package_dir, sub_dir, "config.lua")
                lua_mapper_file_path = os.path.join(lua_package_dir, sub_dir, "mapper.lua")
                if intf == "cli":
                    lua_route_tree_path = os.path.join(lua_package_dir, sub_dir, "route_tree.lua")
                    self.json_to_lua(intf, json_dir, lua_file_path, lua_mapper_file_path, lua_route_tree_path)
                else:
                    self.json_to_lua(intf, json_dir, lua_file_path, lua_mapper_file_path, None)

        # 压缩json文件
        config_dir = os.path.join(self.source_folder, "interface_config")
        for root, dirs, files in os.walk(config_dir):
            for file in files:
                if file.endswith(".json") and not os.path.islink(file):
                    file_path = os.path.join(root, file)
                    with open(file_path, mode='rb') as fp:
                        data = json.load(fp)
                    content = json.dumps(data, separators=(',', ':'))
                    with open(file_path, mode='wb') as fd:
                        fd.write(content.encode('utf-8'))
                    for interface_type in ["web_backend", "redfish", "snmp", "cli"]:
                        if "interface_config/" + interface_type in file_path:
                            json_dir = os.path.join(self.source_folder, "interface_config", interface_type)
                            json_package_dir = os.path.join(self.package_folder, "opt/bmc/apps", interface_type, "interface_config")
                    for interface_type in ["board_info_collector"]:
                        if "interface_config/" + interface_type in file_path:
                            json_dir = os.path.join(self.source_folder, "interface_config", interface_type)
                            json_package_dir = os.path.join(self.package_folder, "opt/bmc/board_info_collector")
                    file_package_path = file_path.replace(json_dir, json_package_dir)                    
                    result =os.path.split(file_package_path)
                    if not os.path.exists(result[0]):
                        os.mkdir(result[0])
                    shutil.copy2(file_path, file_package_path)

    def package_info(self):
        pass